package com.xyzboom.compiler.compiler.ast

import com.xyzboom.compiler.compiler.grammar.*
import com.xyzboom.compiler.compiler.lr.*
import com.xyzboom.compiler.excepitons.compiler.grammar.ASTNodeTypeException
import com.xyzboom.compiler.excepitons.compiler.grammar.TerminatorNameException

/**
 * To ISymbol
 *
 * @return ISymbol represented by the string
 */
fun String.toISymbol(): ISymbol {
    return when {
        isEmpty() -> Terminator.EOF
        get(0).isLowerCase() -> Terminator(this)
        else -> NonTerminator(this)
    }
}

/**
 * Assert astNode type
 *
 * @param shouldBe assert that the type name of astNode should be the given string
 */
fun ASTNode.assertASTNodeType(shouldBe: String) {
    if (typeName != shouldBe) {
        throw ASTNodeTypeException(typeName, shouldBe)
    }
}

/**
 * Get grammar productions
 *
 * @param result empty list or intermediate results
 * @return Productions in grammar
 */
fun ASTNode.getGrammarProductions(result: ArrayList<Production>): ArrayList<Production> {
    assertASTNodeType(GRAMMAR_NAME)
    return result.apply {
        children[0].getProductionsFromProd(result)
        if (children.size == 2)
            children[1].getGrammarProductions(this)
    }
}

/**
 * Get sem grammar productions
 *
 * @param result empty list or intermediate results
 * @return Productions in grammar
 */
fun ASTNode.getSemGrammarProductions(result: ArrayList<Production>): ArrayList<Production> {
    assertASTNodeType(SEM_GRAMMAR_NAME)
    return result.apply {
        var astNodeNow = children[0]
        while (astNodeNow.children.size == 2) {
            astNodeNow.children[0].getProductionsFromProd(result)
            astNodeNow = astNodeNow.children[1]
        }
        astNodeNow.children[0].getProductionsFromProd(result)
    }
}

/**
 * Get ids
 *
 * @param result empty list or intermediate results
 * @return Symbols in Ids
 */
fun ASTNode.getIds(result: ArrayList<ISymbol>): ArrayList<ISymbol> {
    assertASTNodeType(IDS_NAME)
    if (children.isNotEmpty()) {
        result.apply {
            add(getId()!!)//已经判断本节点存在子节点，因此这个函数一定不返回null
            children[1].getIds(this)
        }
    }
    return result
}

/**
 * Get id
 *
 * @return Symbol in id
 */
fun ASTNode.getId(): ISymbol? {
    assertASTNodeType(IDS_NAME)
    return if (children.isEmpty()) {
        null
    } else {
        children[0].assertASTNodeType(ID_NAME)
        val idToken = children[0].token!!
        idToken.info.toISymbol()
    }
}

/**
 * Get ids from production right
 *
 * @return Ids in Production Right
 */
fun ASTNode.getIdsFromProductionRight(): ArrayList<ISymbol> {
    assertASTNodeType(PRODUCTION_RIGHT_NAME)
    return children[0].getIds(ArrayList())
}

/**
 * Get productions from prod
 *
 * @param result empty list or intermediate results
 * @return Productions in Prod
 */
fun ASTNode.getProductionsFromProd(result: ArrayList<Production>): ArrayList<Production> {
    assertASTNodeType(PRODUCTION_NAME)
    val left = children[0].token!!.info.toISymbol()
    if (left is Terminator) {
        throw TerminatorNameException(left.tokenName)
    }
    val idsList = children[2].getIdsListFromProductionRight(ArrayList())
    for (symbolList in idsList) {
        result.add(Production(left as NonTerminator, symbolList))
    }
    return result
}

/**
 * Get ids list from production right
 *
 * @param result empty list or intermediate results
 * @return List of Ids in Production Right
 */

fun ASTNode.getIdsListFromProductionRight(
    result: ArrayList<ArrayList<ISymbol>>,
): ArrayList<ArrayList<ISymbol>> {
    assertASTNodeType(PRODUCTION_RIGHT_NAME)
    return result.apply {
        add(getIdsFromProductionRight())
        if (children.size != 1)
            children[2].getIdsListFromProductionRight(result)
    }
}

/**
 * Get sem exprs
 *
 * @return Semantic Expressions in astNode
 */
fun ASTNode.getSemExprs(): ArrayList<String> {
    assertASTNodeType(SEM_GRAMMAR_NAME)
    return ArrayList<String>().apply {
        var now = children[0] //当前节点为Prods
        add(
            now.children[0].children[3]
                .children[0].token!!.info.removeSurrounding("***")
        )
        while (now.children.size > 1) {
            now = now.children[1]
            add(
                now.children[0].children[3]
                    .children[0].token!!.info.removeSurrounding("***")
            )
        }
    }
}

/**
 * Get global def
 *
 * @return Global Definition in Semantic Grammar
 */
fun ASTNode.getGlobalDef(): String? {
    return if (this.children.size == 2) {
        children[1].children[0].token!!.info.removeSurrounding("***")
    } else null
}